﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using FileTransSystem.Helper;
using FileTransSystem.ViewModel;
using Microsoft.Win32;
using NetworkCommsDotNet;
using NetworkCommsDotNet.Connections;
using NetworkCommsDotNet.Tools;
using Panuon.UI.Silver;

namespace FileTransSystem
{
    /// <summary>
    /// AdminManage.xaml 的交互逻辑
    /// </summary>
    public partial class AdminManage : WindowX
    {
        #region Private Fields

        SQLiteConnection _sqLiteConnection;


        /// <summary>
        /// Data context for the GUI list box
        /// </summary>
        ObservableCollection<ReceivedFile> receivedFiles = new ObservableCollection<ReceivedFile>();

        /// <summary>
        /// References to received files by remote ConnectionInfo
        /// </summary>
        Dictionary<ConnectionInfo, Dictionary<string, ReceivedFile>> receivedFilesDict =
            new Dictionary<ConnectionInfo, Dictionary<string, ReceivedFile>>();

        /// <summary>
        /// Incoming partial data cache. Keys are ConnectionInfo, PacketSequenceNumber. Value is partial packet data.
        /// </summary>
        Dictionary<ConnectionInfo, Dictionary<long, byte[]>> incomingDataCache =
            new Dictionary<ConnectionInfo, Dictionary<long, byte[]>>();

        /// <summary>
        /// Incoming sendInfo cache. Keys are ConnectionInfo, PacketSequenceNumber. Value is sendInfo.
        /// </summary>
        Dictionary<ConnectionInfo, Dictionary<long, SendInfo>> incomingDataInfoCache =
            new Dictionary<ConnectionInfo, Dictionary<long, SendInfo>>();

        /// <summary>
        /// Custom sendReceiveOptions used for sending files. Can be changed via GUI.
        /// </summary>
        SendReceiveOptions customOptions = new SendReceiveOptions<ProtobufSerializer>();

        /// <summary>
        /// Object used for ensuring thread safety.
        /// </summary>
        object syncRoot = new object();

        /// <summary>
        /// Boolean used for suppressing errors during GUI close
        /// </summary>
        static volatile bool windowClosing = false;

        #endregion


        public AdminManage()
        {
            InitializeComponent();
            lbReceivedFiles.DataContext = receivedFiles;
            DataGridFile.DataContext = InitUserFile();
            ButtonRename.DataContext = new DialogsViewModel(DataGridFile);
            ButtonDelete.DataContext = new DialogsViewModel(DataGridFile);
            StartListening();
        }

        private ObservableCollection<SelectableViewModel> InitUserFile()
        {
            ObservableCollection<SelectableViewModel> userFileObservableCollection =
                new ObservableCollection<SelectableViewModel>();
            _sqLiteConnection = GetSqlConnection.Connect();
            DataSet fileSet =
                DatabaseHelper.ExecuteDataSet(_sqLiteConnection, "select * from fileInfo order by fileName",
                    null);
            DataTable table = fileSet.Tables[0];
            for (int i = 0; i < table.Rows.Count; i++)
            {
                userFileObservableCollection.Add(new SelectableViewModel
                {
                    FileName = table.Rows[i]["fileName"].ToString(),
                    FileSize = table.Rows[i]["fileLength"].ToString(),
                    UploadTime = table.Rows[i]["uploadTime"].ToString(),
                    FileMd5 = table.Rows[i]["fileMD5"].ToString(),
                    UploadUser = table.Rows[i]["uploadUser"].ToString(),
                    OwnedBy = table.Rows[i]["ownedBy"].ToString(),
                    FilePath = table.Rows[i]["filePath"].ToString()
                });
            }

            return userFileObservableCollection;
        }


        //开始监听端口
        private void StartListening()
        {
            //Trigger IncomingPartialFileData method if we receive a packet of type 'PartialFileData'
            NetworkComms.AppendGlobalIncomingPacketHandler<byte[]>("PartialFileData", IncomingPartialFileData);
            //Trigger IncomingPartialFileDataInfo method if we receive a packet of type 'PartialFileDataInfo'
            NetworkComms.AppendGlobalIncomingPacketHandler<SendInfo>("PartialFileDataInfo",
                IncomingPartialFileDataInfo);

            //Trigger the method OnConnectionClose so that we can do some clean-up
            NetworkComms.AppendGlobalConnectionCloseHandler(OnConnectionClose);

            //Start listening for TCP connections
            //We want to select a random port on all avaiDataGridFile_OnAutoGeneratingColumn    //an IPEndPoint using IPAddress.Any and port 0.
            Connection.StartListening(ConnectionType.TCP, new IPEndPoint(IPAddress.Any, 0));

            //Write out some useful debugging information the log window
            AddLineToLog("正在初始化。监听ip于以下端口：");
            foreach (IPEndPoint listenEndPoint in Connection.ExistingLocalListenEndPoints(ConnectionType.TCP))
                AddLineToLog(listenEndPoint.Address + ":" + listenEndPoint.Port);
        }

        private void IncomingPartialFileData(PacketHeader header, Connection connection, byte[] data)
        {
            try
            {
                SendInfo info = null;
                ReceivedFile file = null;

                //Perform this in a thread safe way
                lock (syncRoot)
                {
                    //Extract the packet sequence number from the header
                    //The header can also user defined parameters
                    long sequenceNumber = header.GetOption(PacketHeaderLongItems.PacketSequenceNumber);

                    if (incomingDataInfoCache.ContainsKey(connection.ConnectionInfo) &&
                        incomingDataInfoCache[connection.ConnectionInfo].ContainsKey(sequenceNumber))
                    {
                        //We have the associated SendInfo so we can add this data directly to the file
                        info = incomingDataInfoCache[connection.ConnectionInfo][sequenceNumber];
                        incomingDataInfoCache[connection.ConnectionInfo].Remove(sequenceNumber);

                        //Check to see if we have already received any files from this location
                        if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo))
                            receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>());

                        //Check to see if we have already initialised this file
                        if (!receivedFilesDict[connection.ConnectionInfo].ContainsKey(info.Filename))
                        {
                            receivedFilesDict[connection.ConnectionInfo].Add(info.Filename,
                                new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes));
                            AddNewReceivedItem(receivedFilesDict[connection.ConnectionInfo][info.Filename]);
                        }

                        file = receivedFilesDict[connection.ConnectionInfo][info.Filename];
                    }
                    else
                    {
                        //We do not yet have the associated SendInfo so we just add the data to the cache
                        if (!incomingDataCache.ContainsKey(connection.ConnectionInfo))
                            incomingDataCache.Add(connection.ConnectionInfo, new Dictionary<long, byte[]>());

                        incomingDataCache[connection.ConnectionInfo].Add(sequenceNumber, data);
                    }
                }

                //If we have everything we need we can add data to the ReceivedFile
                if (info != null && file != null && !file.IsCompleted)
                {
                    file.AddData(info.BytesStart, 0, data.Length, data);

                    //Perform a little clean-up
                    file = null;
                    data = null;
                    GC.Collect();
                }
                else if (info == null ^ file == null)
                    throw new Exception("Either both are null or both are set. Info is " +
                                        (info == null ? "null." : "set.") + " File is " +
                                        (file == null ? "null." : "set.") + " File is " +
                                        (file.IsCompleted ? "completed." : "not completed."));
            }
            catch (Exception ex)
            {
                //If an exception occurs we write to the log window and also create an error file
                AddLineToLog("Exception - " + ex.ToString());
                LogTools.LogException(ex, "IncomingPartialFileDataError");
            }
        }

        private void IncomingPartialFileDataInfo(PacketHeader header, Connection connection, SendInfo info)
        {
            try
            {
                byte[] data = null;
                ReceivedFile file = null;

                //Perform this in a thread safe way
                lock (syncRoot)
                {
                    //Extract the packet sequence number from the header
                    //The header can also user defined parameters
                    long sequenceNumber = info.PacketSequenceNumber;

                    if (incomingDataCache.ContainsKey(connection.ConnectionInfo) &&
                        incomingDataCache[connection.ConnectionInfo].ContainsKey(sequenceNumber))
                    {
                        //We already have the associated data in the cache
                        data = incomingDataCache[connection.ConnectionInfo][sequenceNumber];
                        incomingDataCache[connection.ConnectionInfo].Remove(sequenceNumber);

                        //Check to see if we have already received any files from this location
                        if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo))
                            receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>());

                        //Check to see if we have already initialised this file
                        if (!receivedFilesDict[connection.ConnectionInfo].ContainsKey(info.Filename))
                        {
                            receivedFilesDict[connection.ConnectionInfo].Add(info.Filename,
                                new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes));
                            AddNewReceivedItem(receivedFilesDict[connection.ConnectionInfo][info.Filename]);
                        }

                        file = receivedFilesDict[connection.ConnectionInfo][info.Filename];
                    }
                    else
                    {
                        //We do not yet have the necessary data corresponding with this SendInfo so we add the
                        //info to the cache
                        if (!incomingDataInfoCache.ContainsKey(connection.ConnectionInfo))
                            incomingDataInfoCache.Add(connection.ConnectionInfo, new Dictionary<long, SendInfo>());

                        incomingDataInfoCache[connection.ConnectionInfo].Add(sequenceNumber, info);
                    }
                }

                //If we have everything we need we can add data to the ReceivedFile
                if (data != null && file != null && !file.IsCompleted)
                {
                    file.AddData(info.BytesStart, 0, data.Length, data);

                    //Perform a little clean-up
                    file = null;
                    data = null;
                    GC.Collect();
                }
                else if (data == null ^ file == null)
                    throw new Exception("Either both are null or both are set. Data is " +
                                        (data == null ? "null." : "set.") + " File is " +
                                        (file == null ? "null." : "set.") + " File is " +
                                        (file.IsCompleted ? "completed." : "not completed."));
            }
            catch (Exception ex)
            {
                //If an exception occurs we write to the log window and also create an error file
                AddLineToLog("Exception - " + ex.ToString());
                LogTools.LogException(ex, "IncomingPartialFileDataInfo");
            }
        }

        private void OnConnectionClose(Connection conn)
        {
            ReceivedFile[] filesToRemove = null;

            lock (syncRoot)
            {
                //Remove any associated data from the caches
                incomingDataCache.Remove(conn.ConnectionInfo);
                incomingDataInfoCache.Remove(conn.ConnectionInfo);

                //Remove any non completed files
                if (receivedFilesDict.ContainsKey(conn.ConnectionInfo))
                {
                    filesToRemove = (from current in receivedFilesDict[conn.ConnectionInfo]
                        where !current.Value.IsCompleted
                        select current.Value).ToArray();
                    receivedFilesDict[conn.ConnectionInfo] =
                        (from current in receivedFilesDict[conn.ConnectionInfo]
                            where current.Value.IsCompleted
                            select current).ToDictionary(entry => entry.Key, entry => entry.Value);
                }
            }

            //Update the GUI
            lbReceivedFiles.Dispatcher.BeginInvoke(new Action(() =>
            {
                lock (syncRoot)
                {
                    if (filesToRemove != null)
                    {
                        foreach (ReceivedFile file in filesToRemove)
                        {
                            receivedFiles.Remove(file);
                            file.Close();
                        }
                    }
                }
            }));

            //Write some useful information the log window
            AddLineToLog("Connection closed with " + conn.ConnectionInfo.ToString());
        }

        private void AddLineToLog(string logLine)
        {
            //Use dispatcher incase method is not called from GUI thread
            ListViewIP.Dispatcher.BeginInvoke(new Action(() =>
            {
                ListViewIP.Items.Add(DateTime.Now.ToShortTimeString() + " - " + logLine);
                ScrollViewerIP.ScrollToBottom();
            }));
        }

        /// <summary>
        /// Adds a new ReceivedFile to the list box data context
        /// </summary>
        /// <param name="file"></param>
        private void AddNewReceivedItem(ReceivedFile file)
        {
            //Use dispatcher incase method is not called from GUI thread
            lbReceivedFiles.Dispatcher.BeginInvoke(new Action(() => { receivedFiles.Add(file); }));
        }


        /// <summary>
        /// Delete the selected ReceivedFile from the application
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DeleteFile_Clicked(object sender, RoutedEventArgs e)
        {
            Button cmd = (Button) sender;
            if (cmd.DataContext is ReceivedFile)
            {
                ReceivedFile fileToDelete = (ReceivedFile) cmd.DataContext;
                lock (syncRoot)
                {
                    //Delete the ReceivedFile from the listbox data context
                    receivedFiles.Remove(fileToDelete);

                    //Delete the ReceivedFile from the internal cache
                    if (receivedFilesDict.ContainsKey(fileToDelete.SourceInfo))
                        receivedFilesDict[fileToDelete.SourceInfo].Remove(fileToDelete.Filename);

                    fileToDelete.Close();
                }

                AddLineToLog("Deleted file '" + fileToDelete.Filename + "' from '" + fileToDelete.SourceInfoStr + "'");
            }
        }

        /// <summary>
        /// Save the selected file to disk
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SaveFile_Clicked(object sender, RoutedEventArgs e)
        {
            Button cmd = (Button) sender;
            if (cmd.DataContext is ReceivedFile fileToSave)
            {
                if (!Directory.Exists(Directory.GetCurrentDirectory() + "\\FileSave\\")) //如果不存在就创建 dir 文件夹  
                    Directory.CreateDirectory(Directory.GetCurrentDirectory() + "\\FileSave\\");
                fileToSave.SaveFileToDisk(Directory.GetCurrentDirectory() + "\\FileSave\\" + fileToSave.Filename);
                AddLineToLog("Saved file '" + fileToSave.Filename + "' from '" + fileToSave.SourceInfoStr + "'");

                _sqLiteConnection = GetSqlConnection.Connect();

                DatabaseHelper.ExecuteNonQuery(_sqLiteConnection, "update fileInfo set filePath=@filePath",
                    new object[1] {Directory.GetCurrentDirectory() + "\\FileSave\\" + fileToSave.Filename});
                RefreshDataGridHelper.Refresh((ObservableCollection<SelectableViewModel>) (DataGridFile.DataContext),
                    "select * from fileInfo order by fileName", null);
            }
            fileScroller.ScrollToBottom();
        }


        private void DataGridFile_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            if (e.Column.Header.Equals("IsSelected"))
            {
                e.Column.IsReadOnly = false;
            }
        }

        private void ButtonOpen_OnClick(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < DataGridFile.Items.Count; i++)
            {
                SelectableViewModel selectableViewModel = (SelectableViewModel) DataGridFile.Items[i];
                if (selectableViewModel.IsSelected && File.Exists(selectableViewModel.FilePath))
                {
                    Process.Start(selectableViewModel.FilePath);
                }
            }
        }


        private void ButtonDelete_OnClick(object sender, RoutedEventArgs e)
        {
        }
    }
}